Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows support #951

Merged
merged 13 commits into from
Apr 15, 2024
Merged

Windows support #951

merged 13 commits into from
Apr 15, 2024

Conversation

mataton
Copy link
Collaborator

@mataton mataton commented Apr 5, 2024

A continuation of PR #949, this PR adds support for Windows.

All issues revolve around two things:

  1. File path separators differing between operating systems. / vs. \
  2. Temporary file handling differing between operating systems.

The first issue is solved with a straightforward fix using os.join() and os.path.sep.

The second issue is more complex. In Windows, the name of a NamedTemporaryFile object cannot be used to open the file a second time, while the named temporary file is still open. See this SO, and the Python docs. The strategy is to use the delete=False parameter to prevent the temporary file from being deleted when it is closed, and then manually delete the file when the test is done using it.

Copy link
Member

@wasade wasade left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @mataton!!! This is super helpful, and glad to see the changes seem relatively minor! The primary comments are capturing the tmp filename for unlinking in tearDown

fh.write(biom1)
fh.flush()
self.biom_table1 = biom.load_table(fh.name)
os.unlink(fh.name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could self.temporary_fh_name be set within the scope of the context manager, and move the os.unlink to tearDown? The reason for the former is the current approach relies on scope creep which works but borders on surprising behavior. The latter is important as, if a test fails, cleanup will only reliably occur if the unlink is performed by tearDown.

fh.write(biom1)
fh.flush()
self.biom1 = load_table(fh.name)
os.unlink(fh.name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here per comment above

fh.write(biom1)
fh.flush()
self.biom_table1 = load_table(fh.name)
fh.close()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was it necessary to close here but not in the other TestCases? And same here re unlinking. If it's viable to just unlink within the context manager than that may be easier than the tearDown

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you close the file before unlinking within the context manager, then it works. Otherwise you get a PermissionError at the unlinking step. Looking at the code now, I think the idea of setting self.temporary_fh_name in the context manager and putting the unlink in tearDown is the best solution.

save_table(t, tmpfile.name)
obs = load_table(tmpfile.name)
self.assertEqual(obs, t)
os.unlink(tmpfile.name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

h5 = h5py.File(tmpfile.name, 'w')
t.to_hdf5(h5, 'tests')
h5.close()

h5 = h5py.File(tmpfile.name, 'r')
obs = Table.from_hdf5(h5)
h5.close()
os.unlink(tmpfile.name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are creating the temporary files outside of a setUp/tearDown context, so the solution involving putting the unlink in tearDown won't work here. The reason the closes are necessary within the context manager is that without them unlink will throw PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'C:\\Users\\me\\AppData\\Local\\Temp\\tmpg4pk3ink' . My understanding is that the context manager will close tmpfile when it is finished, but it won't close h5. Exactly why this is the case I'm unsure of.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the case because I set delete=False during the file creation, so a file will not be deleted when it is closed. But it seems on windows that file handles must be closed before the file may be deleted.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this solution works because it closes all file handles, including tmpfile and h5, and then deletes the file using unlink. I'm not sure of a better solution for this particular case and the other similar cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue is edge case if test excepts prior to unlink. A common strategy for this type of stuff:

def setUp(self):
    self.temp_files = []
def tearDown(self):
    for f in self.temp_files:
        os.unlink(f)
def test_some_test(self):
    ...
    self.temp_files.append(blah)
    ...

h5 = h5py.File(tmpfile.name, 'w')
t.to_hdf5(h5, 'tests', creation_date=current)
h5.close()

h5 = h5py.File(tmpfile.name, 'r')
obs = Table.from_hdf5(h5)
self.assertEqual(obs.create_date, current)
h5.close()
os.unlink(tmpfile.name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

h5 = h5py.File(tmpfile.name, 'w')
t.to_hdf5(h5, 'tests')
h5.close()
os.unlink(tmpfile.name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

h5 = h5py.File(tmpfile.name, 'w')
t.to_hdf5(h5, 'tests')
h5.close()
os.unlink(tmpfile.name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...I'll stop flagging on these

'generated-by',
'creation-date',
'shape', 'nnz']))
'format-url',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change in indent here looks incorrect as it should be aligned with the [?

@mataton
Copy link
Collaborator Author

mataton commented Apr 11, 2024

@wasade Essentially it looks like there are two scenarios. One where all the tests in whichever test.py file require the temporary files. In this case, the setUp/tearDown solution I feel is best. This is true in test_add_metadata.py, test_summarize_table.py, and test_table_converter.py.

The other scenario is that not all of the tests within the test.py require the temporary files, so using setUp/tearDown framework doesn't make sense here. The current solution in those files I think might be the most appropriate, considering my previous comments.

@mataton mataton changed the title WIP: Windows support Windows support Apr 11, 2024
- name: Build wheels (py ${{ matrix.pyver }}) Windows
if: matrix.os == 'windows-latest'
env:
CIBW_ARCHS_WINDOWS: "amd64 win32 arm64"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that 32-bit windows? If so, does the build actually work?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...this repository does not need to support 32-bit architectures

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it is 32 bit windows and that it works, although @qiyunzhu wrote this section of the code so he may know more than me.

h5 = h5py.File(tmpfile.name, 'w')
t.to_hdf5(h5, 'tests')
h5.close()

h5 = h5py.File(tmpfile.name, 'r')
obs = Table.from_hdf5(h5)
h5.close()
os.unlink(tmpfile.name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue is edge case if test excepts prior to unlink. A common strategy for this type of stuff:

def setUp(self):
    self.temp_files = []
def tearDown(self):
    for f in self.temp_files:
        os.unlink(f)
def test_some_test(self):
    ...
    self.temp_files.append(blah)
    ...

@wasade
Copy link
Member

wasade commented Apr 13, 2024

Thanks, @mataton. I still think we do want unlink in tearDown as that ensures it runs even in the event of an exception, does that make sense? It's relatively minor here though as these are still going into TMPDIR

@mataton
Copy link
Collaborator Author

mataton commented Apr 15, 2024

@wasade Your suggested changes make sense, and there was actually already a to_remove = [] in the setUp functions that I didn't notice previously, which I used. Just had to add the proper tearDown to a couple files. It should be good now.

@wasade
Copy link
Member

wasade commented Apr 15, 2024

Will merge once aarch64 completes

@wasade
Copy link
Member

wasade commented Apr 15, 2024

...thank you @mataton, this is super helpful!!! One last request, could you add a changelog note?

@mataton
Copy link
Collaborator Author

mataton commented Apr 15, 2024

Happy to contribute!

ChangeLog.md Outdated Show resolved Hide resolved
@wasade wasade merged commit 738d240 into biocore:master Apr 15, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants